Skip to content

feat(clustering): propagate contentDescription to marker options for accessibility - #1035

Open
dkhawk wants to merge 3 commits into
mainfrom
fix/clustering-content-description
Open

dkhawk wants to merge 3 commits into
mainfrom
fix/clustering-content-description

Conversation

@dkhawk

@dkhawk dkhawk commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Support setting accessibility contentDescription on clustered map markers, resolving accessibility issues where TalkBack and UIAutomator announce generic "Map Marker" instead of meaningful pin information.

Fixes #683, #706

Changes

  • Clustering.kt: Added contentDescription property to ClusteringMarkerProperties and its @Composable fun ClusteringMarkerProperties helper function.
  • ClusterRenderer.kt:
    • In onBeforeClusterItemRendered: Propagate custom contentDescription (or fallback to ClusterItem.title) into MarkerOptions.contentDescription.
    • In onBeforeClusterRendered: Propagate custom cluster contentDescription into MarkerOptions.contentDescription.
    • In InvalidatingComposeView: Include properties.contentDescription in the LaunchedEffect invalidation key set so dynamic updates re-render the marker.
  • GoogleMapViewClusteringTests.kt:
    • testClusteringPropagatesItemTitleToMarkerContentDescription: Asserts that uncustomized cluster items automatically propagate item.title to MarkerOptions.contentDescription.
    • testClusteringMarkerPropertiesCustomContentDescription: Asserts that explicit ClusteringMarkerProperties(contentDescription = ...) is propagated to MarkerOptions.contentDescription.

Verification

  • Unit tests & Android Lint: ./gradlew :maps-compose-utils:testDebugUnitTest lint (clean pass).
  • Instrumented tests: Ran GoogleMapViewClusteringTests on Android emulator (medium_phone, API 36) with 100% pass rate (5/5 tests).

Reviewers

@kikoso @LoyalAbbas

…accessibility

Support setting accessibility contentDescription on clustered map markers,
resolving accessibility issues where TalkBack and UIAutomator announce
generic "Map Marker" instead of meaningful pin information.

- Add contentDescription to ClusteringMarkerProperties and its helper composable.
- Propagate custom contentDescription or ClusterItem.title to MarkerOptions in ComposeUiClusterRenderer.
- Add regression tests in GoogleMapViewClusteringTests verifying both default item title fallback and explicit custom contentDescription.

Fixes #683, #706

@kikoso kikoso left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking this up, it is a long-standing accessibility issue! Two comments, the binary compatibility one is the important one.

anchor: Offset? = null,
zIndex: Float? = null,
rotation: Float? = null,
contentDescription: String? = null,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a parameter to a public composable changes its JVM signature, so anything compiled against 9.0.x that calls ClusteringMarkerProperties(...) would fail with NoSuchMethodError until it is recompiled. Could we keep the old signature as a @Deprecated(level = DeprecationLevel.HIDDEN) overload that delegates to the new one? That keeps it binary compatible, as AGENTS.md asks for public API changes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Added the 3-parameter ClusteringMarkerProperties overload marked @Deprecated(level = DeprecationLevel.HIDDEN) delegating to the 4-parameter version, preserving binary compatibility for pre-compiled binaries.

}

private fun MarkerOptions.getContentDescription(): String? {
val field = MarkerOptions::class.java.declaredFields.firstOrNull {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads obfuscated fields from MarkerOptions (zzb, zzc, zzr), so it can silently start reading a different field after a Play services update. In the first test the expected value is also the item title, so if it ever picks up the title field the test would pass even without the fix. What about moving the fallback logic into a small internal function and testing it directly, or at least giving the item a title that differs from the expected description?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great suggestions.

  1. Extracted the resolution logic into resolveItemContentDescription and resolveClusterContentDescription internal functions, and added dedicated unit tests in ClusterRendererContentDescriptionTest.kt covering fallback, override, and null cases without touching Android or Play services.
  2. In the instrumentation test, removed all hardcoded obfuscated field names (zzb, zzc, zzr) and added a dynamic field counter and distinct titles vs descriptions to eliminate any chance of false positives.

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Coverage (unit tests)

Compared against 85a56aa on main (#1034).

Module Line % Change Branch % Change
maps-compose 0.00% no change 0.00% no change
maps-compose-utils 2.33% 🔺 +0.29 0.94% 🔺 +0.45
maps-compose-widgets 0.00% no change 0.00% no change
TOTAL 0.49% 🔺 +0.08 0.17% 🔺 +0.08

Line and branch coverage from unit test reports. History is recorded on the coverage-history branch after each merge to main.

@googlemaps-bot

Copy link
Copy Markdown
Contributor

Code Coverage

Overall Project 44.54% -0.07% 🍏
Files changed 83.44% 🍏

Module Coverage
maps-app 56.49% -0.56% 🍏
Files
Module File Coverage
maps-app ClusterRenderer.kt 71.05% -1.65% 🍏
Clustering.kt 54.42% 🍏

dkhawk added 2 commits October 9, 2026 14:21
…nd test robustness

- Retain 3-parameter ClusteringMarkerProperties overload marked @deprecated(level = DeprecationLevel.HIDDEN) for binary compatibility.
- Extract description resolution logic to internal resolveItemContentDescription and resolveClusterContentDescription functions with dedicated unit tests.
- Remove obfuscated Proguard field references in GoogleMapViewClusteringTests and ensure distinct title vs description values.
@googlemaps-bot

googlemaps-bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Coverage (instrumentation tests)

Overall Project 44.5% -0.18% 🍏
Files changed 65.58% 🍏

Module Coverage
maps-app 56% -1.51% 🍏
Files
Module File Coverage
maps-app ClusterRenderer.kt 71.01% -1.82% 🍏
Clustering.kt 53.6% -1.5% ❌

@dkhawk
dkhawk marked this pull request as ready for review October 9, 2026 21:30
@dkhawk
dkhawk requested a review from kikoso October 9, 2026 21:30

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Clustering: Missing contentDescription on the clustering pins

3 participants